Today I worked with some friends at integrating diginsight to a new web API.
In this post I propose the steps we followed applied to the sample SampleWebApi01
available into the telemetry_samples
repository.
You can use the steps below to obtain diginsight text based streams on Log4Net and the appservice Console.
STEP01: Add references to diginsight packages
I start adding the following 3 references:
PackageReference Include="Diginsight.AspnetCore" Version="3.0.0-alpha.205" />
<PackageReference Include="Diginsight.Diagnostics" Version="3.0.0-alpha.205" />
<PackageReference Include="Diginsight.Diagnostics.Log4Net" Version="3.0.0-alpha.205" /> <
where:
- Diginsight.Diagnostics
is the core engine for application flow rendering
- Diginsight.Diagnostics.Log4Net
integrates Log4Net file logs
- Diginsight.AspnetCore
allows support for Dynamic Logging and Dynamic Configuration.
STEP02: Add the Observability ActivitySource
to your projects
System Diagnostics
emits telemetry by means of ActivitySource
classes.
In our solution we’ll choose to use one ActivitySource
class for every project, defined as below:
internal static class Observability
{
public static readonly ActivitySource ActivitySource = new(Assembly.GetExecutingAssembly().GetName().Name!);
}
STEP03: Configure the startup sequence with ‘AddObservability()’ method
At this point we can configure the Log4Net
file stream logger and the Console
logger within the service startup sequence.
we use an AddObservability
extension method:
public static void Main(string[] args)
{
var app = default(WebApplication);
var builder = WebApplication.CreateBuilder(args);
.Host.ConfigureAppConfigurationNH();
builder
.Services.AddObservability(builder.Configuration); // Diginsight: registers loggers
builder
//...
//... register services
//...
= builder.Build(); logger.LogDebug("app = builder.Build();");
app
.LogInformation("Configure the HTTP request pipeline.");
loggerif (app.Environment.IsDevelopment())
{
.UseSwagger();
app.UseSwaggerUI(c =>
app{
.SwaggerEndpoint("/swagger/v1/swagger.json", "KnowledgeAPI v1");
c.OAuthClientId(azureAd.ClientId);
c.OAuthUsePkce();
c.OAuthScopeSeparator(" ");
c});
}
.UseHttpsRedirection();
app
.UseAuthorization();
app
.MapControllers();
app}
.Run();
app}
where builder.Services.AddObservability(builder.Configuration);
registers loggers for Log4net
and the application Console
as shown below:
public static class AddObservabilityExtension
{
public static IServiceCollection AddObservability(this IServiceCollection services, IConfiguration configuration)
{
// registers http context accessor
// this is used to manage dynamic logging and dynamic configuration
// when calls land to the controllers methods
.AddHttpContextAccessor();
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
services
// registers Logging providers for the Console and Log4Net
.AddLogging(
services=>
loggingBuilder {
.AddConfiguration(configuration.GetSection("Logging"));
loggingBuilder.ClearProviders();
loggingBuilder
if (configuration.GetValue("AppSettings:ConsoleProviderEnabled", true))
{
// registers Logging providers for the Console
.AddDiginsightConsole(configuration.GetSection("Diginsight:Console").Bind);
loggingBuilder}
if (configuration.GetValue("AppSettings:Log4NetProviderEnabled", false))
{
// registers Logging providers for Log4Net
.AddDiginsightLog4Net(
loggingBuilderstatic sp =>
{
= sp.GetRequiredService<IHostEnvironment>();
IHostEnvironment env string fileBaseDir = env.IsDevelopment()
? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify)
: $"{Path.DirectorySeparatorChar}home";
return new IAppender[]
{
new RollingFileAppender()
{
= Path.Combine(fileBaseDir, "LogFiles", "Diginsight", typeof(Program).Namespace!),
File = true,
AppendToFile = false,
StaticLogFileName = RollingFileAppender.RollingMode.Composite,
RollingStyle = @".yyyyMMdd.\l\o\g",
DatePattern = 1000,
MaxSizeRollBackups = "100MB",
MaximumFileSize = new FileAppender.MinimalLock(),
LockingModel = new DiginsightLayout()
Layout {
= "{Timestamp} {Category} {LogLevel} {TraceId} {Delta} {Duration} {Depth} {Indentation|-1} {Message}",
Pattern },
},
};
},
static _ => Level.All
);
}
}
);
// Loads options for diginsight activities
// this decides which activities should be traced into the text based streams
// for the console and Log4Net
.ConfigureClassAware<DiginsightActivitiesOptions>(configuration.GetSection("Diginsight:Activities"))
services.DynamicallyConfigureClassAwareFromHttpRequestHeaders<DiginsightActivitiesOptions>();
// Register DefaultDynamicLogLevelInjector for support of Dynamic logging
.AddDynamicLogLevel<DefaultDynamicLogLevelInjector>();
services
return services;
}
}
Please, note that DiginsightActivitiesOptions
are read from Diginsight:Activities
the configuration section shown below:
Note that Diginsight:Activities
settings are registered with ConfigureClassAware
to ensure that such configurations can be specified with class level granularity (ref: Use ClassAware configurations)
Also note that AddHttpContextAccessor
and AddDynamicLogLevel
are used to ensure support for Dynamic logging (ref: Use dynamic logging):
.AddHttpContextAccessor();
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddDynamicLogLevel<DefaultDynamicLogLevelInjector>(); services
Working code is available into SampleWebApi01
sample, within telemetry_samples
repository.
STEP04 (Opt): add Instrumentation to the startup sequence
Startup sequence may be tricky and often hides startup issues that are very difficult to debug.
Diginsight provides full observability of the startup sequence by means of the DeferredLoggerFactory
.
In particular, the DeferredLoggerFactory
:
- gathers the execution flow until the the standard logging system is setup.
- Flushes recorded telemetry upon services creation, as soon as ILogger<>
providers are installed.
here we create the deferred logger factory at application startup:
public static IDeferredLoggerFactory LoggerFactory;
static Program()
{
= new() { LogActivities = true };
DiginsightActivitiesOptions activitiesOptions = new DeferredLoggerFactory(activitiesOptions: activitiesOptions);
IDeferredLoggerFactory deferredLoggerFactory .ActivitySourceFilter = (activitySource) => activitySource.Name.StartsWith($"DS.");
deferredLoggerFactory= deferredLoggerFactory;
LoggerFactory }
deferredLoggerFactory implements in memory recording of the application flow, until ILogger services are created.
After ILogger services creation, FlushOnCreateServiceProvider
can be used to register Flush of the recorded application flow and redirection of the deferredLoggerFactory to the registered LoggerFactory.
The real Log flush is provided at services creation time by means of the Diginsight ServiceProvider. the Diginsight ServiceProvider is registered by means of UseDiginsightServiceProvider
method as shown below.
The snippet below shows the main
method where the startup flow is made observable by means of FlushOnCreateServiceProvider
and UseDiginsightServiceProvider
.
public static void Main(string[] args)
{
= LoggerFactory.CreateLogger(typeof(Program));
ILogger logger
var app = default(WebApplication);
using (var activity = Observability.ActivitySource.StartMethodActivity(logger, new { args }))
{
var builder = WebApplication.CreateBuilder(args); logger.LogDebug("builder = WebApplication.CreateBuilder(args);");
.Host.ConfigureAppConfigurationNH(); logger.LogDebug("builder.Host.ConfigureAppConfigurationNH();");
builder.Services.AddObservability(builder.Configuration); // Diginsight: registers loggers
builder.Services.FlushOnCreateServiceProvider(LoggerFactory); // Diginsight: registers startup log flush
builder
// ...
// services registration omitted.
// ...
var webHost = builder.Host.UseDiginsightServiceProvider(); // Diginsight: Flushes startup log and initializes standard log
= builder.Build(); logger.LogDebug("app = builder.Build();");
app
.LogInformation("Configure the HTTP request pipeline.");
loggerif (app.Environment.IsDevelopment())
{
.UseSwagger();
app.UseSwaggerUI(c =>
app{
.SwaggerEndpoint("/swagger/v1/swagger.json", "KnowledgeAPI v1");
c.OAuthClientId(azureAd.ClientId);
c.OAuthUsePkce();
c.OAuthScopeSeparator(" ");
c});
}
.UseHttpsRedirection();
app
.UseAuthorization();
app
.MapControllers();
app
}
.Run();
app}
STEP05: add Instrumentation log to methods and return values
We are now ready to add automatic instrumentation to methods and return values:
here is an example method start
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
using var activity = Observability.ActivitySource.StartMethodActivity(logger);
here is an example method completion where the result value is added to the method closing row:
?.SetOutput(ret);
activityreturn ret;
}
the real code may looks as shown below:
here is the resulting flow for the service startup and an API call: